package org.spiderflow.core.expression.parsing.ast.access;

import org.spiderflow.core.expression.ExpressionError;
import org.spiderflow.core.expression.ExpressionTemplate;
import org.spiderflow.core.expression.ExpressionTemplateContext;
import org.spiderflow.core.expression.parsing.Span;
import org.spiderflow.core.expression.parsing.ast.Expression;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * Represents a map or array element access of the form <code>mapOrArray[keyOrIndex]</code>. Maps and arrays may only be read
 * from.
 *
 * @author zsh
 */
public class MapOrArrayAccess extends Expression {
    private final Expression mapOrArrayExpression;
    private final Expression keyOrIndexExpression;

    public MapOrArrayAccess(Span span, Expression mapOrArrayExpression, Expression keyOrIndexExpression) {
        super(span);
        this.mapOrArrayExpression = mapOrArrayExpression;
        this.keyOrIndexExpression = keyOrIndexExpression;
    }

    /**
     * Returns an expression that must evaluate to a map or array.
     **/
    public Expression getMapOrArray() {
        return mapOrArrayExpression;
    }

    /**
     * Returns an expression that is used as the key or index to fetch a map or array element.
     **/
    public Expression getKeyOrIndex() {
        return keyOrIndexExpression;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Object evaluate(ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {
        Object mapOrArray = getMapOrArray().evaluate(template, context);
        if (mapOrArray == null) {
            return null;
        }
        Object keyOrIndex = getKeyOrIndex().evaluate(template, context);
        if (keyOrIndex == null) {
            return null;
        }
        if (mapOrArray instanceof Map) {
            return ((Map) mapOrArray).get(keyOrIndex);
        } else if (mapOrArray instanceof List) {
            return getValueOfList(mapOrArray, keyOrIndex);
        } else {
            if (!(keyOrIndex instanceof Number)) {
                throw ExpressionError.error("Array index must be an integer, but was " + keyOrIndex.getClass().getSimpleName(), getKeyOrIndex().getSpan());
            }
            int index = ((Number) keyOrIndex).intValue();
            return getValueOfArray(mapOrArray, index);
        }
    }

    private Object getValueOfArray(Object mapOrArray, int index) {
        if (mapOrArray instanceof int[]) {
            return ((int[]) mapOrArray)[index];
        } else if (mapOrArray instanceof float[]) {
            return ((float[]) mapOrArray)[index];
        } else if (mapOrArray instanceof double[]) {
            return ((double[]) mapOrArray)[index];
        } else if (mapOrArray instanceof boolean[]) {
            return ((boolean[]) mapOrArray)[index];
        } else if (mapOrArray instanceof char[]) {
            return ((char[]) mapOrArray)[index];
        } else if (mapOrArray instanceof short[]) {
            return ((short[]) mapOrArray)[index];
        } else if (mapOrArray instanceof long[]) {
            return ((long[]) mapOrArray)[index];
        } else if (mapOrArray instanceof byte[]) {
            return ((byte[]) mapOrArray)[index];
        } else if (mapOrArray instanceof String) {
            return Character.toString(((String) mapOrArray).charAt(index));
        } else {
            return ((Object[]) mapOrArray)[index];
        }
    }

    private Object getValueOfList(Object mapOrArray, Object keyOrIndex) {

        if (!(keyOrIndex instanceof Number)) {
            throw ExpressionError.error("List index must be an integer, but was " + keyOrIndex.getClass().getSimpleName(), getKeyOrIndex().getSpan());
        }
        int index = ((Number) keyOrIndex).intValue();
        return ((List) mapOrArray).get(index);
    }
}
